<?php

/**
 * Static class for error page rendering; doubles as Mz namespace exception class
 *
 * @author    Mon Zafra <monzee at gmail>
 * @copyright (c)2009 Mon Zafra
 * @package   Mz
 * @license   http://monzee.wordpress.com/license New BSD
 * @version   $Id: Exception.php 38 2009-02-18 17:12:39Z monzee $
 * @todo Fix truncation logic
 */
class Mz_Exception extends Exception
{
    /**
     * Render php template
     *
     * @param string $tpl  Full path+filename of template
     * @param array  $vars Array of var=>value pairs
     */
    static public function renderTemplate($tpl, $vars)
    {
        extract($vars);
        include($tpl);
    }

    /**
     * Prepare template vars, then call render
     *
     * @param Exception $exp  Exception object to render
     * @param string    $tpl  Full path+filename of error page template
     * @param string    $base Base url
     */
    static public function render(Exception $exp, $tpl = 'error.phtml', $base = '')
    {
        $vars = array();
        $vars['base'] = $base;
        $vars['class'] = self::_normalize(get_class($exp));
        $vars['message'] = $exp->getMessage();
        $vars['code'] = $exp->getCode();
        $vars['file'] = $exp->getFile();
        $vars['line'] = $exp->getLine();
        $vars['trace' ] = self::_generateTrace($exp->getTrace());
        self::renderTemplate($tpl, $vars);
    }

    static protected function _normalize($name)
    {
        $name = preg_replace('/(?<=[a-zA-Z])(?=[A-Z])/x', ' ', str_replace('_', ' ', $name));
        return ucwords($name);
    }

    static protected function _generateTrace($trace)
    {
        $ret = '<ol>';
        $c = 0;
        foreach ($trace as $call) {
            $ret .= '<li>';
            $file = isset($call['file']) ? $call['file'] : null;
            $line = isset($call['line']) ? $call['line'] : null;
            if (!empty($file) && !empty($line)) {
                $ret .= "<div class=\"location\">$file : $line</div>";
            } else {
                $ret .= '<div class="location"><em>magic call</em></div>';
            }
            $func = '<tt>';
            $func .= empty($call['class'])
                ? '<strong>' . $call['function'] . '</strong>'
                : '<strong>' . $call['class'] . '</strong>'. $call['type'] . '<strong>' . $call['function'] . '</strong>';
            $args = $call['args'];
            $details = '';
            foreach ($args as &$arg) {
                $arg = self::_varToString($arg, $details);
            }
            $args = implode(', ', $args);
            $ret .= $func . '( ' . $args . ' )' . '</tt>';
            if (!empty($details)) {
                $details = trim($details);
                $ret .= " <a href=\"javascript:toggle('det-$c')\">parameter details</a><br />\n"
                     .  "<pre id=\"det-$c\" class=\"details\">$details\n</pre>";
            }
            $ret .= '</li>' . PHP_EOL;
            $c++;
        }
        $ret .= "<li><tt>{main}</tt></li>\n</ol>";
        return $ret;
    }

    static protected function _varToString($var, &$details)
    {
        switch (gettype($var)) {
            case 'string':
                $var = '\'' . addcslashes($var, "'") . '\'';
                break;
            case 'boolean':
                $var = '<em class="var-bool">' . ($var ? 'true' : 'false') . '</em>';
                break;
            case 'NULL':
                $var = '<em class="var-null">NULL</em>';
                break;
            case 'object':
                $details .= print_r($var, true) . "\n\n";
                $var = '<em class="var-object">object</em>(<strong>' . get_class($var) . '</strong>)';
                break;
            case 'array':
                $tmp = '<em class="var-array">array</em>(';
                foreach ($var as $key => $elem) {
                    if (is_string($key)) {
                        $tmp .= "'$key' => ";
                    }
                    $tmp .= self::_varToString($elem, $details) . ', ';
                    if (strlen($tmp) > 80) {
                        $tmp .= '...';
                        $details .= print_r($var, true) . "\n\n";
                        break;
                    }
                }
                $tmp = (', ' == substr($tmp, -2) ? substr($tmp, 0, -2) : $tmp) . ')';
                $var = $tmp;
                break;
            default:
                break;
        }
        return $var;
    }

}
